#!/usr/bin/env python
"""Project Tracker

Helps to track time spent on projects/subprojects/tasks. "Timesheet" is actually a tree
where any node can have a list of start-stop pairs, as well as subnodes.

Sample data:
{'BigProject': {'Subproject 1': {'Task 1': {'timesheet': [(datetime.datetime(2007, 3, 29, 8, 38, 8, 515000),
                                                          datetime.datetime(2007, 3, 29, 8, 44, 39, 483000)),
                                                       (datetime.datetime(2007, 3, 29, 9, 18, 55, 61000),
                                                        datetime.datetime(2007, 3, 29, 9, 19, 1, 233000))]}},
                 'Subproject 2': {'Task 2': {'timesheet': [(datetime.datetime(2007, 3, 29, 9, 19, 38, 874000),
                                                            datetime.datetime(2007, 3, 29, 9, 49, 33, 280000)),
                                                          (datetime.datetime(2007, 3, 29, 9, 50, 12, 233000),
                                                          datetime.datetime(2007, 3, 29, 9, 50, 23, 93000))]}}}}


Author: Volodymyr Orlenko, orlenko@gmail.com

"""


import sys
import os
import datetime
import traceback
import pprint


tracData = {}
FILENAME = 'tracker.dat'


def main():
   init()
   while 1:
      what = raw_input('> ')
      if len(what.strip()) > 1:
         track(what)
      else:
         dispatch.get(what, help)()
         
         
def init():
   global tracData
   global FILENAME
   try:
      tracData = os.path.exists(FILENAME) and eval(file(FILENAME).read()) or {}
   except:      
      traceback.print_exc()
      tracData = {}
      print 'tracData reset to empty!'


def help():
   print """
   Use one of these commands:
   projectname/taskname Start tracking time for the <task> within <project>. To stop tracking this task, press Enter.
   p                    Print brief report on all projects and tasks
   t                    Print detailed timesheet for every project
   q                    Quit program
   h                    Print this screen
   """


def quit():
   sys.exit(0)


def track(path):
   global tracData
   parts = path.split('/')
   node = tracData
   for part in parts:
      part = part.strip()
      if part not in node:
         if part == 'timesheet': part = 'timesheet ' # precaution
         node[part] = {}
      node = node[part]
   start = datetime.datetime.now()
   raw_input('Tracking time for "%s" (press Enter to stop tracking it)...' % path)
   if 'timesheet' not in node:
      node['timesheet'] = []
   node['timesheet'].append((start, datetime.datetime.now()))
   save()
   
   
def save():
   global tracData
   global FILENAME
   pprint.pprint(tracData, open(FILENAME, 'w'))
   
   
def reportBrief():
   global tracData
   sortedKeys = sorted(tracData.keys(), key=lambda x: x.lower())
   for key in sortedKeys:
      value = tracData[key]
      timeReport = TimeReport(value)
      print '%s: %s' % (key, format(timeReport.total))
      print 'Details by subprojects:\n%s' % timeReport.formatByProject()
      print
      
      
def reportTimesheet():
   global tracData
   sortedKeys = sorted(tracData.keys(), key=lambda x: x.lower())
   for key in sortedKeys:
      value = tracData[key]
      timeReport = TimeReport(value)
      print '%s: %s' % (key, format(timeReport.total))
      print 'Details by time:\n%s' % timeReport.formatByTime()
      print
   


def format(duration):
   retval = ''
   parts = [('day%s', duration.days),
            ('hour%s', duration.seconds / 3600),
            ('minute%s', (duration.seconds % 3600) / 60),
            ('second%s', duration.seconds % 60)]
   for label, value in parts:
      if value or retval:
         if retval:
            retval += ', '
         retval += '%s %s' % (value, (label % s(value)))
   return retval


def s(val):
   return val != 1 and 's' or ''


class TimeReport(object):
   
   def __init__(self, timetree):
      self.timetree = timetree
      self.total = datetime.timedelta(0, 0, 0)
      self.timesheet = {}
      self.projsheet = {}
      self._flatten(timetree, '.')

   def _flatten(self, node, path):
      if 'timesheet' in node:
         for start, finish in node['timesheet']:
            duration = finish - start
            self.total += duration
            self.timesheet[start] = dict(path=path,
                                         start=start,
                                         finish=finish,
                                         duration=duration)
            if path not in self.projsheet:
               self.projsheet[path] = datetime.timedelta(0, 0, 0)
            self.projsheet[path] += duration
      for name, subnode in node.iteritems():
         if name != 'timesheet':
            self._flatten(node[name], path + '/' + name)

   def formatByTime(self):
      timeformat = '%Y-%m-%d %H:%M:%S'
      return '\n'.join(['%(start)s - %(finish)s (%(duration)s)\t%(path)s' \
                         % {'start': self.timesheet[key]['start'].strftime(timeformat),
                            'finish': self.timesheet[key]['finish'].strftime(timeformat),
                            'duration': format(self.timesheet[key]['duration']),
                            'path': self.timesheet[key]['path']} \
                        for key in sorted(self.timesheet.keys())])
   
   def formatByProject(self):
      return '\n'.join(['%(path)s\t%(duration)s' \
                        % {'path': key,
                           'duration': format(self.projsheet[key])} \
                       for key in sorted(self.projsheet.keys())])


dispatch = {'p': reportBrief,
            't': reportTimesheet,
            'q': quit,
            'h': help}


if __name__ == '__main__':
   main()
